package uk.ac.jisc.depositmo.widgets;

/**
 * Copyright (c) 2011, University of Southampton
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * # Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 * # Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation 
 *     and/or other materials provided with the distribution.
 * # Neither the name of the University of Southampton nor the names of its
 *     contributors may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;

import uk.ac.jisc.depositmo.ui.cocoa.CocoaUIEnhancer;

/**
 * 
 * @author Mark Borkum <mark.borkum@gmail.com>
 *
 */
public class ApplicationDialog {
	/**
	 * Returns the default command-line arguments for a new instance of the application.  
	 * @return  the default command-line arguments for the application
	 */
	private static String[] createCommandLineArguments() {
		return new String[0];
	}
	
	/**
	 * Returns the default Display for a new instance of the application.
	 * @return  the default Display for the application
	 */
	private static Display createDisplay() {
		// Detect the current display.
		Display display = Display.getCurrent();
		
		// If the current display is not available... 
		if (display == null) {
			// Set the application name and version. The setApp***() methods MUST be invoked BEFORE the first display instance is constructed.  
			Display.setAppName(getAppName());
			Display.setAppVersion(getAppVersion());
			
			// Update the current display.
			display = new Display();
		}
		
		return display;
	}
	
	/**
	 * Returns the name of the application.  
	 * @return  the name of the application
	 */
	public static String getAppName() {
		return "depositMO";
	}
	
	/**
	 * Returns the summary of the application.
	 * @return  the summary of the application
	 */
	public static String getAppSummary() {
		return "Modus operandi for repository deposits";
	}
	
	/**
	 * Returns the version of the application.
	 * @return  the version of the application
	 */
	public static String getAppVersion() {
		return "0.0.3";
	}
	
	/**
	 * Specifies if the application should confirm when the shell ("window") is closed.
	 */
	private static final boolean ALWAYS_CONFIRM_CLOSE = true;
	
	/**
	 * Specifies the horizontal alignment of the shell ("window") for the application.
	 * @see  SWT#LEFT
	 * @see  SWT#RIGHT
	 */
	private static final int HORIZONTAL_ALIGN = SWT.RIGHT;
	
	/**
	 * Specifies the vertical alignment of the shell ("window") for the application.
	 * @see  SWT#TOP
	 * @see  SWT#BOTTOM
	 */
	private static final int VERTICAL_ALIGN = SWT.TOP;
	
	private CocoaUIEnhancer cocoaUIEnhancer;
	
	private List<DepositDialog> depositDialogs;
	
	private Shell shell;
	
	/**
	 * Constructs a new instance of this class using the default display and command-line arguments.
	 */
	public ApplicationDialog() {
		this(createDisplay(), createCommandLineArguments());
	}
	
	/**
	 * Constructs a new instance of this class using the supplied display and default command-line arguments.
	 * @param display  the display to create this shell ("window") on
	 */
	public ApplicationDialog(Display display) {
		this(display, createCommandLineArguments());
	}
	
	/**
	 * Constructs a new instance of this class using the supplied command-line arguments and default display.
	 * @param argv  the command-line arguments
	 */
	public ApplicationDialog(String[] argv) {
		this(createDisplay(), argv);
	}
	
	/**
	 * Constructs a new instance of this class using the supplied display and command-line arguments.
	 * @param display  the display to create this shell ("window") on
	 * @param argv  the command-line arguments
	 */
	public ApplicationDialog(Display display, String[] argv) {
		this.setDepositDialogs(new LinkedList<DepositDialog>());
		
		final Shell shell = createFileTransferImageDialog(display);
		
		this.setShell(shell);
		
		if (CocoaUIEnhancer.OK) {
			final CocoaUIEnhancer enhancer = createCocoaUIEnhancer(display);
			
			this.setCocoaUIEnhancer(enhancer);
		}
		
		// Construct the hook for "open document" events.
		final Listener openDocumentListener = new Listener() {
			public void handleEvent(Event e) {
				if (hasNewMenuListener()) {
					File[] files = new File[] { new File(e.text) };
					
					Event event = new Event();
					event.data = files;
					getNewMenuListener().handleEvent(event);
				}
			}
		};
		
		// Add a hook to the display to detect "open document" events. 
		display.addListener(SWT.OpenDocument, openDocumentListener);
		
		// If at least one command-line argument was supplied...
		if (argv.length > 0) {			
			for (String arg : argv) {
				// Treat each command-line argument as a relative path to a file.
				String filename = arg.trim();
				
				// Strip quotes (if present).
				while (true) {
					if ((filename.startsWith("'") && filename.endsWith("'")) || (filename.startsWith("\"") && filename.endsWith("\""))) {
						filename = filename.substring(1, filename.length() - 2).trim();
					} else {
						break;
					}
				}
				
				// Construct an "open document" event.  
				final Event event = new Event();
				event.text = filename;
				openDocumentListener.handleEvent(event);
			}
		}
	}
	
	private boolean aboutIsOpen = false;
	
	/**
	 * Returns the listener that is triggered when the "about" menu-item is selected.
	 * @return  the listener
	 */
	public Listener getAboutMenuListener() {
		return new Listener() {
			public void handleEvent(Event e) {
				if (aboutIsOpen) {
					return;
				} else {
					aboutIsOpen = true;
					
					if (CocoaUIEnhancer.OK) {
						getCocoaUIEnhancer().setEnabledAboutMenu(false);
						getCocoaUIEnhancer().setEnabledPreferencesMenu(false);
					}
				}
				
				final AboutDialog aboutDialog = new AboutDialog(getShell());
				
				aboutDialog.getShell().addListener(SWT.Dispose, new Listener() {
					public void handleEvent(Event e1) {
						aboutIsOpen = false;
						
						if (CocoaUIEnhancer.OK) {
							getCocoaUIEnhancer().setEnabledAboutMenu(hasAboutMenuListener());
							getCocoaUIEnhancer().setEnabledPreferencesMenu(hasPreferencesMenuListener());
						}
					}
				});
				
				aboutDialog.getShell().open();
			}
		};
	}
	
	/**
	 * Returns the listener that is triggered when the "new" menu-item is selected. 
	 * @return  the listener
	 */
	public Listener getNewMenuListener() {
		return new Listener() {
			public void handleEvent(Event e) {
				File[] files = (File[]) e.data;
				
				final DepositDialog depositDialog = new DepositDialog(getShell(), files);
				
				final Menu menuBar = createMenuBar(depositDialog.getShell());
				
				depositDialog.getShell().setMenuBar(menuBar);
				
				getDepositDialogs().add(depositDialog);

				depositDialog.getShell().addListener(SWT.Dispose, new Listener() {
					public void handleEvent(Event e) {
						getDepositDialogs().remove(depositDialog);
					}
				});
				
				if ((files.length == 0) || (depositDialog.getFileTransferTable().getFileCount() > 0)) {
					depositDialog.getShell().open();
					
					// Bug-fix
					depositDialog.getShell().forceActive();
					depositDialog.getShell().forceFocus();
				} else {
					depositDialog.getShell().dispose();
				}
			}
		};
	}
	
	/**
	 * Returns the listener that is triggered when the "preferences" menu-item is selected.
	 * @return  the listener
	 */
	public Listener getPreferencesMenuListener() {
		// This method returns an undefined value. 
		// If a listener is not defined, then the menu-item will *not* be enabled, i.e., it will remain disabled.
		return null;
	}
	
	/**
	 * Returns the listener that is triggered when the "quit" menu-item is selected.
	 * @return  the listener
	 */
	public Listener getQuitMenuListener() {
		return new Listener() {
			public void handleEvent(Event e) {
				final int depositDialogCount = getDepositDialogs().size();
				
				if ((depositDialogCount == 0) && !ALWAYS_CONFIRM_CLOSE) {
					e.doit = true;
				} else { // Otherwise...
					final MessageBox messageBox = new MessageBox(shell, SWT.ICON_QUESTION | SWT.YES | SWT.NO);
					
					messageBox.setText("Quit" + " " + getAppName());
					messageBox.setMessage("Are you sure you want to quit " + getAppName() + "?" + ((depositDialogCount == 0) ? "" : "\n" + depositDialogCount + " deposit" + ((depositDialogCount == 1) ? "" : "s") + " will be cancelled."));
			        
					e.doit = (messageBox.open() == SWT.YES);
				}
			}
		};
	}
	
	/**
	 * Determines if a listener is available for the "about" menu-item. 
	 * @return  true if an "about" listener is available, otherwise false
	 */
	private boolean hasAboutMenuListener() {
		return this.getAboutMenuListener() != null;
	}
	
	/**
	 * Determines if a listener is available for the "new" menu-item.
	 * @return  true if a "new" listener is available, otherwise false
	 */
	private boolean hasNewMenuListener() {
		return this.getNewMenuListener() != null;
	}
	
	/**
	 * Determines if a listener is available for the "preferences" menu-item.
	 * @return  true if a "preferences" listener is available, otherwise false
	 */
	private boolean hasPreferencesMenuListener() {
		return this.getPreferencesMenuListener() != null;
	}
	
	/**
	 * Determines if a listener is available for the "quit" menu-item.
	 * @return  true if a "quit" listener is available, otherwise false
	 */
	public boolean hasQuitMenuListener() {
		return this.getQuitMenuListener() != null;
	}
	
	private CocoaUIEnhancer createCocoaUIEnhancer(Display display) {
		final CocoaUIEnhancer enhancer = new CocoaUIEnhancer(display, getAppName());
		
		if (hasAboutMenuListener()) {
			enhancer.setActionAboutMenu(getAboutMenuListener());
			
			enhancer.setEnabledAboutMenu(true);
		} else {
			enhancer.setEnabledAboutMenu(false);
		}
		
		if (hasPreferencesMenuListener()) {
			enhancer.setActionPreferencesMenu(getPreferencesMenuListener());
			
			enhancer.setEnabledPreferencesMenu(true);
		} else {
			enhancer.setEnabledPreferencesMenu(false);
		}
		
		enhancer.setActionQuitMenu(getQuitMenuListener());
		
		return enhancer;
	}
	
	private Shell createFileTransferImageDialog(Display display) {
		/**
		 * 1. Create widgets.
		 */
		
		final Shell shell = new Shell(display, SWT.DIALOG_TRIM | SWT.MIN);
		
		final FileTransferImage fileTransferImage = new FileTransferImage(shell) {
			@Override
			public void filesDropped(File[] files) {
				if (hasNewMenuListener()) {
					Event event = new Event();
					event.data = files;
					getNewMenuListener().handleEvent(event);
				}
			}
		};
		
		/**
		 * 2. Set layout. 
		 */
		
		shell.setMinimumSize(fileTransferImage.getSize());
		
		shell.setText(getAppName());
		
		shell.pack();
		
		int x = 0; // HORIZONTAL_ALIGN == SWT.LEFT
		int y = 0; // VERTICAL_ALIGN == SWT.TOP
		
		if (HORIZONTAL_ALIGN == SWT.RIGHT) {
			x = display.getClientArea().width - shell.getSize().x;
		}
		
		if (VERTICAL_ALIGN == SWT.BOTTOM) {
			y = display.getClientArea().height - shell.getSize().y;
		}
		
		shell.setLocation(x, y);
		
		/**
		 * 3. Hook listeners.
		 */
		
		if (hasQuitMenuListener()) {
			shell.addListener(SWT.Close, getQuitMenuListener());
		}
		
		/**
		 * 4. Construct menu-bar.
		 */
		
		final Menu menuBar = createMenuBar(shell);
		
		shell.setMenuBar(menuBar);
		
		return shell;
	}
	
//	private List<MenuItem> viewFileTransferImageMenuItems = new LinkedList<MenuItem>();
//	
//	private List<MenuItem> getViewFileTransferImageMenuItems() {
//		return viewFileTransferImageMenuItems;
//	}
	
	private Menu createMenuBar(final Shell shell) {
		final Menu menuBar = new Menu(shell, SWT.BAR);
		
		final Menu fileMenu = createMenu(shell, menuBar, "&File");
		
		createMenuItem(fileMenu, "&New\tMod1+N", SWT.MOD1 + 'N', new Listener() {
			public void handleEvent(Event e) {
				if (hasNewMenuListener()) {
					Event event = new Event();
					event.data = new File[0];
					getNewMenuListener().handleEvent(event);
				}
			}
		});
		
		if (shell.getParent() != null) {
			new MenuItem(fileMenu, SWT.SEPARATOR);
			
			createMenuItem(fileMenu, "&Close\tMod1+W", SWT.MOD1 + 'W', new Listener() {
				public void handleEvent(Event e) {
					shell.close();
				}
			});
		}
		
		if (!CocoaUIEnhancer.OK) {
			new MenuItem(fileMenu, SWT.SEPARATOR);
			
			createMenuItem(fileMenu, "&Quit\tAlt+F4", SWT.ALT + SWT.F4, getQuitMenuListener());
		}
		
//		final Menu viewMenu = createMenu(shell, menuBar, "&View");
//		
//		final MenuItem viewFileTransferImageMenuItem = createMenuItem(viewMenu, SWT.CHECK, "Display Drop Zone", SWT.None, new Listener() {
//			public void handleEvent(Event e) {
//				final Shell shell = getShell();
//				
//				if (shell != null) {
//					boolean visible = shell.getVisible();
//					
//					for (MenuItem menuItem : getViewFileTransferImageMenuItems()) {
//						menuItem.setSelection(!visible);
//					}
//					
//					shell.setVisible(!visible);
//				}
//			}
//		});
//		
//		viewFileTransferImageMenuItem.setSelection(true);
//		
//		getViewFileTransferImageMenuItems().add(viewFileTransferImageMenuItem);
//		
//		viewFileTransferImageMenuItem.addListener(SWT.Dispose, new Listener() {
//			public void handleEvent(Event e) {
//				getViewFileTransferImageMenuItems().remove(viewFileTransferImageMenuItem);
//			}
//		});
		
		final Menu helpMenu = createMenu(shell, menuBar, "&Help");
		
		createMenuItem(helpMenu, "&About" + (CocoaUIEnhancer.OK ? " " + getAppName() : ""), SWT.None, getAboutMenuListener());
		
		return menuBar;
	}
	
	private Menu createMenu(final Shell shell, final Menu menuBar, String text) {
		final MenuItem menuItem = new MenuItem(menuBar, SWT.CASCADE);
		
		menuItem.setText(text);
		
		final Menu menu = new Menu(shell, SWT.DROP_DOWN);
		
		menuItem.setMenu(menu);
		
		return menu;
	}
	
	private MenuItem createMenuItem(final Menu menu, String text, int accelerator, final Listener listener) {
		return createMenuItem(menu, SWT.PUSH, text, accelerator, listener);
	}
	
	private MenuItem createMenuItem(final Menu menu, int style, String text, int accelerator, final Listener listener) {
		final MenuItem menuItem = new MenuItem(menu, style);
		
		menuItem.setText(text);
		
		if (accelerator != SWT.None) {
			menuItem.setAccelerator(accelerator);
		}
		
		if (listener != null) {
			menuItem.addListener(SWT.Selection, listener);
			
			menuItem.setEnabled(true);
		} else {
			menuItem.setEnabled(false);
		}
		
		return menuItem;
	}

	private void setShell(Shell shell) {
		this.shell = shell;
	}

	public Shell getShell() {
		return shell;
	}

	private void setDepositDialogs(List<DepositDialog> depositDialogs) {
		this.depositDialogs = depositDialogs;
	}

	public List<DepositDialog> getDepositDialogs() {
		return depositDialogs;
	}

	private void setCocoaUIEnhancer(CocoaUIEnhancer enhancer) {
		this.cocoaUIEnhancer = enhancer;
	}

	public CocoaUIEnhancer getCocoaUIEnhancer() {
		return cocoaUIEnhancer;
	}
	
	public void run() {
		final Shell shell = this.getShell();
		
		if (shell != null) {
			final Display display = shell.getDisplay();
			
			if (display != null) {
				shell.open();
				
				while (!shell.isDisposed()) {
					if (!display.readAndDispatch()) {
						display.sleep();
					}
				}

				display.dispose();
			}
		}
	}
}
